package com.example.lock.redis.impl;

import com.example.lock.entity.RedisLockInfo;
import com.example.lock.redis.RedisLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @Description: redis 实现分布式锁 全局续命
 * @Author: mingtian
 * @CreateDate: 2020/12/11 16:14
 * @Version: 1.0
 */
@Component
public class GlobalRedisLockImpl implements RedisLock {

    /**
     * 打印日志
     */
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 存放获取锁对象
     */
    private static Map<Thread, RedisLockInfo> cacheThreadLock = new ConcurrentHashMap<>();

    /**
     * 声明定时任务
     */
    private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();


    /**
     * redis 锁 key
     */
    private String redisLockKey = "redisLockKey";

    /**
     * 锁超时时间
     */
    private Long lockTimeOut = 30L;

    /**
     * 重试获取超时时间
     */
    private Long retryTimeOut = 30000L;

    /**
     * 续命次数上限
     */
    private Integer MAX_LIFE_COUNT = 3;

    /**
     * (全局续命)初始化续命线程
     * 1.可以通过全局续命(通过定时任务)
     * 2.局部续命(获取锁成功之后单独开启线程进行续命)
     */
    public GlobalRedisLockImpl() {
        //开始定时任务实现续命
        this.scheduledExecutorService.scheduleAtFixedRate(new LifeExtensionThread(), 0, 5, TimeUnit.SECONDS);
    }

    /**
     * 获取锁
     *
     * @return
     */
    @Override
    public boolean getLock() {

        // 从缓存中获取锁对象
        Thread currentThread = Thread.currentThread();
        RedisLockInfo redisLockInfo = cacheThreadLock.get(currentThread);
        if (!Objects.isNull(redisLockInfo) && redisLockInfo.isState()) {
            logger.info(">>>在缓存中获取到锁,可以直接复用!<<<");
            return true;
        }
        try {
            // 开始执行时间
            long startTime = System.currentTimeMillis();
            for (; ; ) {
                // 1.获取锁 设置超时时间 避免 死锁现象
                Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(redisLockKey, redisLockKey, lockTimeOut, TimeUnit.MINUTES);
                if (lock) {
                    logger.info(">>>获取锁成功!<<<");
                    cacheThreadLock.put(currentThread, new RedisLockInfo(currentThread, lockTimeOut));
                    return true;
                }
                // 执行结束时间
                long endTime = System.currentTimeMillis();
                // 判断重试超时时间
                if (endTime - startTime > retryTimeOut) {
                    logger.info(">>>重试时间已经过了,不再继续重试!<<<");
                    return false;
                }
                // 休眠 500 毫秒之后 在重试
                Thread.sleep(500);
                return false;
            }
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 释放锁
     */
    @Override
    public boolean releaseLock() {
        // A线程创建的锁 B线程执行释放锁 是谁创建的锁，就应该给谁删除。 lua脚本实现删除
        return stringRedisTemplate.delete(redisLockKey);
    }

    /**
     * (全局)续命线程
     * 当我们的获取锁的jvm业务执行时间>过期key的超时时间 应该实现续命：
     * 当key过期的时候：走事件回调到客户端。---时间在延长
     * 延长过期key 应该是提前的。通过定时任务提前延长过期key
     * 算法实现：
     * 开启一个定时任务，每隔一段时间检测获取到锁的线程，延长该过期key的时间
     */
    private class LifeExtensionThread implements Runnable {
        @Override
        public void run() {
            try {
                // 遍历缓存锁对象进行续命处理
                cacheThreadLock.forEach((key, lockInfo) -> {
                    // 如何判断线程是否执行完毕
                    Thread lockThread = lockInfo.getLockThread();
                    if (lockThread.isInterrupted()) {
                        logger.info(">>>当前线程已经停止,不需要进行续命处理<<<");
                        // 从缓存中移除已经停止的线程
                        cacheThreadLock.remove(key);
                    }
                    // 判断续命次数 如果达到续命上限 则不再进行续命处理
                    Integer lifeCount = lockInfo.getLifeCount();
                    logger.info("第[{}]次续命", lifeCount);
                    if (lifeCount > MAX_LIFE_COUNT) {
                        logger.info(">>>已经续命多次,不在进行续命处理 释放锁<<<");
                        // 1.回滚事务
                        // 2.释放锁
                        releaseLock();
                        // 3.停止该线程
                        lockThread.isInterrupted();
                        // 4.移除缓存
                        cacheThreadLock.remove(key);
                        return;
                    }
                    // 进行续命处理
                    stringRedisTemplate.expire(redisLockKey, lockInfo.getExpire(), TimeUnit.SECONDS);
                });
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("errorInfo:{}", e);
            }
        }
    }
}